#!/usr/bin/env python
# -*- coding:utf-8 -*-

# src/grujieclient/gruijiecontrol.py

# Copyright (C) 2009 Gong Han <gong AT fedoraproject.org>

# This file is part of gRuijieClient.

# gRuijieClient is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.

# gRuijieClient is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with gRuijieClient. If not, see <http://www.gnu.org/licenses/>.

import os
import sys
import gtk
import signal
import time
import pty
import thread
import fcntl

class RuijieClientControl:
    """
    RuijieClient console control class
    @author: Gong Han
    """
    def __init__(self, cmd, txt_buf, tv):

        self._cmd = cmd
        self._txt_buf = txt_buf
        self._txt_view = tv
        self._pty = PyPty('/bin/sh', 1)
        
        self.init_var()

    def __del__(self):
        if self._pty is not None:
            self._pty.close()

    def connect(self):
        # initialize variables
        self.init_var()
        # execute command
        self._pty.send_command(self._cmd+'\n')
        # set start time
        self._stime = time.time()

        while self.serv_msg is None and time.time() - self._stime < 3:

            index = self._buf.find('\n')
            if index != -1:
                # update connection status
                # note: status messages are with prefix '@@'
                if self._buf_line.startswith('@@'):
                    self._con_status += 1
                # retrieve server message
                # note: server message is with prefix '##'
                if self._buf_line.startswith('##'):
                    self.serv_msg = self._buf_line[19:]
                    self._con_status += 1
                self._buf_line = self._buf[:index]
                # insert buf_line to text_buf
                self._txt_buf.insert(self._txt_buf.get_end_iter(), 
                                     self._buf_line)
                # delete detected string line
                self._buf = self._buf[index + 1:]

            # read a character from pipe
            if self._pty.outputPresent:
                self._buf += self._pty.read_output();

            # if connection has been established, terminate loop
            if self.serv_msg is not None:
                # insert successful message in text_view 
                self._txt_buf.insert(self._txt_buf.get_end_iter(), 
                                     '\n** CONNECTED')

            # present messages to text_view in time
            # top secret of GTK+!
            while gtk.events_pending():
                gtk.main_iteration(False)
                # update scroll position
                self._txt_view.scroll_to_iter(self._txt_buf.get_end_iter(), 0)
        else:
            # exceeded connection time limit
            if self._con_status < 4:
                self.term_pty_command()
                self._txt_buf.insert(self._txt_buf.get_end_iter(), 
                                     '\n** AUTH FAILED: '
                                     + str(self.get_status())
                                     + '\n' )
                return False
            else:
                return True

    def disconnect(self):
        if self._con_status > 2:
            self._is_working = False
            self.term_pty_command()
            self._con_status = 0
            self._txt_buf.insert(self._txt_buf.get_end_iter(), 
                                 '\n** LOGOUTED')
            return True
        else:
            return False

    def get_status(self):
        return self._con_status

    def get_pid(self):
        return self._pid

    def term_pty_command(self):
        os.system('killall --signal 2 %s' %self._cmd)

    def init_var(self):
        self._buf_line = ''
        self._con_status = 0
        self.serv_msg = None
        self._buf = ''
        self._is_working = True

class PyPty:
    """
    RuijieClient PTY class
    @author: Gong Han
    """
    def __init__(self, cmd, bufsize):
        self._cmd = cmd
        self._bufsize = bufsize
        (self.master, self.slave) = pty.openpty()
        (self.pid, self.fd) = pty.fork()
        if self.pid == 0:
            try:
                pty.spawn(self._cmd, self.master_read, self.slave_read)
            except:
                print >> sys.stderr, "Fail in executing"
                sys.exit(1)
        else:
            attr = fcntl.fcntl(self.fd, fcntl.F_GETFL)
            attr |= os.O_NONBLOCK
            fcntl.fcntl(self.fd, fcntl.F_SETFL, attr)
            self.outputLock = thread.allocate_lock()
            self.running = True
            self.outputText = ""
            self.outputPresent = False
            thread.start_new_thread(self.keep_reading, ())

    def send_command(self, cmd):
        #pid = os.fork()
        os.write(self.fd, cmd)

    def keep_reading(self):
        data = ''
        while 1:
            while self.running:
                # try to read
                try:
                    data = os.read(self.fd, self._bufsize)
                    break
                except:
                    time.sleep(1)
            # lock while writing
            self.outputLock.acquire()
            while 1:
                self.outputText += data
                try:
                    data = os.read(self.fd, self._bufsize)
                except:
                    break
            self.outputPresent = True
            self.outputLock.release()

    def read_output(self):
        # lock while reading
        self.outputLock.acquire()
        data = self.outputText
        self.outputText = ""
        self.outputPresent = False
        self.outputLock.release()

        return data

    def master_read(self, masterfd):
        #os.write(self.fd, raw_input() +"\n")
        data = os.read(masterfd, 1024)
        #print "Data:",data
        return data

    def slave_read(self, slavefd):
        #os.write(self.fd, raw_input() +"\n")
        data = os.read(slavefd, 1024)
        #print "Data:",data
        return data

    def close(self):
        self.running = False
        os.kill(self.pid, signal.SIGINT)

if __name__ == '__main__':
    print RuijieClientControl
    print PyPty